﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Threading;
using OpenTap.Plugins.Interfaces.ACPsu;
using OpenTap.Plugins.Interfaces.Common;

namespace OpenTap.Plugins.AcPsu
{
    public abstract class AcPsuBase : ScpiInstrument, IACPsu
    {
        #region Settings
        #endregion

        protected int VoltRangeInit;
        protected int RangeLowSetVal;
        protected int RangeHiSetVal;
        protected double RangeLowMaxVolt;
        protected double RangeHiMaxCurr;

        /// <summary>
        ///     Checks if PSU is connected. Throws an exception if it's not connected.
        /// </summary>
        /// <exception cref="InvalidOperationException">Throws this exception if PSU is not connected</exception>
        protected virtual void CheckIfConnected()
        {
            if (!IsConnected) throw new InvalidOperationException("Psu is not connected!");
        }

        /// <inheritdoc />
        public virtual void ClearOutputProtection()
        {
            SetOutputState(EState.Off);
            ScpiCommand("OUTP:PROT:CLE");
            Log.Info("Output OFF and Protection cleared.");
            QueryErrWithExc(true, "ClearOutputProtection");
        }

        /// <summary>
        ///     Closes the connection to the instrument. Assumes connection is open.
        ///     Override add: Set power output state to OFF with closing.
        /// </summary>
        public override void Close()
        {
            SetOutputState(EState.Off);
            base.Close();
        }

        /// <inheritdoc />
        public virtual double GetCurrentLimit()
        {
            CheckIfConnected();
            return ScpiQuery<double>("CURR?");
        }

        /// <inheritdoc />
        public abstract double GetCurrentLimitDc();

        /// <inheritdoc />
        public virtual EState GetCurrentProtectionStatus()
        {
            CheckIfConnected();
            // Questionable Event register's bit 1 used to over current status
            var status = ScpiQuery<short>("STAT:QUES:COND?") & 2;
            return status == 0 ? EState.Off : EState.On;
        }

        /// <inheritdoc />
        public virtual double GetFrequency()
        {
            CheckIfConnected();
            return ScpiQuery<double>("FREQ?");
        }

        /// <inheritdoc />
        public virtual int GetProtectionStatusAll()
        {
            CheckIfConnected();
            return ScpiQuery<int>("STAT:QUES:COND?");
        }

        /// <inheritdoc />
        public abstract double GetVoltageDcProtectionLimitLower();

        /// <inheritdoc />
        public abstract double GetVoltageDcProtectionLimitUpper();

        /// <inheritdoc />
        public abstract EState GetVoltageDcProtectionState();

        /// <inheritdoc />
        public virtual double GetVoltageLevel()
        {
            CheckIfConnected();
            return ScpiQuery<double>("VOLT?");
        }

        /// <inheritdoc />
        public abstract double GetVoltageLevelDc();

        /// <inheritdoc />
        public abstract EVoltageMode GetVoltageMode();

        /// <inheritdoc />
        public virtual double GetVoltageRange()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("VOLT:RANG?");

            // Ranges are RangeLoV = 135 or RangeHiV = 270
            if (result >= 270.0)
            {
                return 270;
            }
            if (result >= 135.0)
            {
                return 135;
            }
            throw new ApplicationException("Voltage range does not exist in GetVoltageRange()");
        }

        /// <inheritdoc />
        public virtual double MeasureActualPowerW()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("MEAS:POW:AC?");
            QueryErrWithExc(true, "MeasureActualPowerW: " + result);
            return result;
        }

        /// <inheritdoc />
        public virtual double MeasureApparentPowerVA()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("MEAS:POW:AC:APP?");
            QueryErrWithExc(true, "MeasureApparentPowerVA: " + result);
            return result;
        }

        /// <inheritdoc />
        public virtual double MeasureCurrent()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("MEAS:CURR:AC?");
            QueryErrWithExc(true, "MeasureCurrent: " + result);
            return result;
        }

        /// <inheritdoc />
        public abstract double MeasureCurrentDc();

        /// <inheritdoc />
        public virtual double MeasurePeakCurrent()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("MEAS:CURR:AMPL:MAX?");
            QueryErrWithExc(true, "MeasurePeakCurrent: " + result);
            return result;
        }

        /// <inheritdoc />
        public virtual double MeasurePowerFactor()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("MEAS:POW:AC:PFAC?");
            QueryErrWithExc(true, "MeasurePowerFactor: " + result);
            return result;
        }

        /// <inheritdoc />
        public virtual double MeasureReactivePowerVAR()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("MEAS:POW:AC:REAC?");
            QueryErrWithExc(true, "MeasureReactivePowerVAR: " + result);
            return result;
        }

        /// <inheritdoc />
        public virtual double MeasureVoltage()
        {
            CheckIfConnected();
            var result = ScpiQuery<double>("MEAS:VOLT:AC?");
            QueryErrWithExc(true, "MeasureVoltage: " + result);
            return result;
        }

        /// <inheritdoc />
        public abstract double MeasureVoltageDc();

        // override because method in ScpiInstrument throws System.Runtime.InteropServices.COMException
        // when using NI-VISA & RSR232 interface
        /// <inheritdoc />
        public new List<ScpiError> QueryErrors(bool suppressLogMessages = false, int maxErrors = 1000)
        {
            var scpiErrorList = new List<ScpiError>();
            var bufferCleared = false;

            // Have maximum allowed loops to avoid stucking to infinite loop.
            for (var i = 0; i < maxErrors; i++)
            {
                var errorStr = ScpiQuery("SYST:ERR?");
                if (!suppressLogMessages) Log.Info(errorStr);

                var split = errorStr.Split(',');
                ScpiError error;
                error.Code = Convert.ToInt32(split[0]);
                char[] charsToTrim = { '"', '\r' };
                error.Message = split[1].Trim(charsToTrim);

                if (error.Code != 0)
                {
                    scpiErrorList.Add(error);
                }
                else
                {
                    bufferCleared = true;
                    i = maxErrors;
                }
            }

            if (bufferCleared == false)
                throw new ApplicationException("Could not clear errors within" + maxErrors + "tries!");

            return scpiErrorList;
        }

        public void Clear()
        {
            throw new NotImplementedException();
        }

        /// <inheritdoc />
        /// <exception cref="InvalidOperationException">Throws this exception with info message, if error occurred.</exception>
        public virtual void QueryErrWithExc(bool ifErrRunException = true, string infoMessToShow = "Info message to be shown with exception")
        {
            var errors = QueryErrors(true, 100);
            if (errors.Count > 0)
            {
                string errCollection = "";
                Log.Info("Error count: " + errors.Count + " with: " + infoMessToShow);
                for (int i = 0; i < (errors.Count); i++)
                {
                    Log.Info("ERRor[" + errors[i].Code + "] " + errors[i].Message);
                    errCollection = String.Concat(errCollection, ", \n", errors[i].Code, " ", errors[i].Message);
                }

                if (ifErrRunException)
                {
                    if (errors.Count == 1)
                        throw new InvalidOperationException("ERRor occurred with " + infoMessToShow + " as follow" + errCollection);
                    throw new InvalidOperationException("ERRors occurred with " + infoMessToShow + " as follow" + errCollection);
                }
            }
        }

        /// <summary>
        ///     Reads PSU critical range limits in initial phase to be used later, if range changed.
        /// </summary>
        protected virtual void ReadRangCritLimits(int voltRangeToSet, int rangeLowVolt, int rangeHiVolt)
        {
            if (RangeLowSetVal == 0 || RangeHiSetVal == 0)  // Run this query within initializing and after mode(Ac/Dc) change.
            {
                QueryErrWithExc(false, "");                 // Cleans error queue

                if (voltRangeToSet > rangeLowVolt)
                {
                    ScpiCommand("VOLT:RANG " + rangeLowVolt);
                    QueryErrWithExc(true, "ReadRangCritLimits VOLT:RANG " + rangeLowVolt);
                    RangeLowMaxVolt = ScpiQuery<double>("VOLT? MAX");
                    QueryErrWithExc(true, "ReadRangCritLimits VOLT? MAX");

                    ScpiCommand("VOLT:RANG " + rangeHiVolt);
                    QueryErrWithExc(true, "ReadRangCritLimits VOLT:RANG " + rangeHiVolt);
                    RangeHiMaxCurr = ScpiQuery<double>("CURR? MAX");
                    QueryErrWithExc(true, "ReadRangCritLimits CURR? MAX");
                }
                else
                {
                    ScpiCommand("VOLT:RANG " + rangeHiVolt);
                    QueryErrWithExc(true, "ReadRangCritLimits VOLT:RANG " + rangeHiVolt);
                    RangeHiMaxCurr = ScpiQuery<double>("CURR? MAX");
                    QueryErrWithExc(true, "ReadRangCritLimits CURR? MAX");

                    ScpiCommand("VOLT:RANG " + rangeLowVolt);
                    QueryErrWithExc(true, "ReadRangCritLimits VOLT:RANG " + rangeLowVolt);
                    RangeLowMaxVolt = ScpiQuery<double>("VOLT? MAX");
                    QueryErrWithExc(true, "ReadRangCritLimits VOLT? MAX");
                }

                QueryErrWithExc(true, "ReadRangCritLimits");

                RangeLowSetVal = rangeLowVolt;
                RangeHiSetVal = rangeHiVolt;
            }
        }

        /// <inheritdoc />
        public new virtual void Reset()
        {
            CheckIfConnected();
            base.Reset();
            QueryErrWithExc(true, "Reset");
        }

        /// <inheritdoc />
        public virtual int SelfTest()
        {
            CheckIfConnected();
            return Convert.ToInt32(ScpiQuery("*TST?"));
        }

        /// <inheritdoc />
        public abstract void SetCurrent(double currentAmps);

        /// <inheritdoc />
        public abstract void SetCurrentDc(double currentAmps);

        /// <inheritdoc />
        public virtual void SetCurrentProtectionState(EState state)
        {
            CheckIfConnected();

            if (!Enum.IsDefined(typeof(EState), state))
                throw new InvalidEnumArgumentException("state", (int)state, typeof(EState));

            ScpiCommand(EState.On == state ? "CURR:PROT:STAT ON" : "CURR:PROT:STAT OFF");
            QueryErrWithExc(true, "SetCurrentProtectionState: " + state);
        }

        /// <inheritdoc />
        public virtual void SetFrequency(double freqHertz)
        {
            CheckIfConnected();

            if (freqHertz <= 0)
                throw new ArgumentOutOfRangeException(nameof(freqHertz));

            ScpiCommand("FREQ " + freqHertz.ToString("#.000", CultureInfo.InvariantCulture));
            QueryErrWithExc(true, "SetFrequency: " + freqHertz);
        }

        /// <inheritdoc />
        public virtual void SetOutputState(EState state)
        {
            if (!Enum.IsDefined(typeof(EState), state))
                throw new InvalidEnumArgumentException(nameof(state), (int)state, typeof(EState));

            // Purpose of this loop is to confirm succes run and write log.
            // Log will help with investigating of malfunctions with Psu Output control On/Off.
            const int tryMax = 4;
            for (var i = 0; i < tryMax + 1; i++)
            {
                ScpiCommand(EState.On == state ? "OUTP:STAT ON" : "OUTP:STAT OFF");
                WaitForOperationComplete();
                QueryErrWithExc(true, "SetOutputState: " + state + " with try: " + (i + 1));

                Thread.Sleep(i > 5 ? 500 : i * 100); // 0, 100, 200, 300, 400, 500, 500, 500...

                EState readState;
                var readBuff = ScpiQuery<int>("OUTP:STAT?");

                switch (readBuff)
                {
                    case 0:
                        readState = EState.Off;
                        break;

                    case 1:
                        readState = EState.On;
                        break;

                    default:
                        throw new InvalidOperationException("OUTP:STAT? answer(" + readBuff + "). Wanted answers: 0 or 1");
                }

                if (state == readState) // Success
                {
                    Log.Info("AcPsu Output state(" + readState + ") with try " + (i + 1));
                    break;
                }

                if (i >= tryMax)
                {
                    Log.Error("AcPsu Output state(" + readState + ") is not as wanted(" + state + ") with try " + (i + 1) + ". ==> Next InvalidOperationException");
                    throw new InvalidOperationException("AcPsu Output state(" + readState + ") is not as wanted(" + state + ") with try " + (i + 1) + ".\n SHUTDOWN THE AC-POWER MANUALLY!!!");
                }

                Log.Warning("WARNING! Output state(" + readState + ") is not as wanted(" + state + ") with try " + (i + 1));
                Thread.Sleep(i > 3 ? 1000 : i * i * 100 + 100); // 100, 200, 500, 1000, 1000, 1000...
            }
        }

        /// <inheritdoc />
        public void SetVoltage(double voltageV)
        {
            CheckIfConnected();
            ScpiCommand("VOLT " + voltageV.ToString("#.000", CultureInfo.InvariantCulture));
            QueryErrWithExc(true, "SetVoltage: " + voltageV);
        }

        /// <inheritdoc />
        public abstract void SetVoltageDc(double voltageDc);

        /// <inheritdoc />
        public virtual void SetVoltageRange(int voltageRangeV)
        {
            CheckIfConnected();

            // Check critical limit-value, before rangechange
            if (voltageRangeV > RangeLowSetVal)                  // Case: High voltage & Low current.
            {
                if (ScpiQuery<double>("CURR?") > RangeHiMaxCurr) // If current limit is bigger than Max of the range to be changed,
                {
                    ScpiCommand("CURR " + RangeHiMaxCurr);       // current limit will be decreased to range-max-current first
                }
                ScpiCommand("VOLT:RANG " + RangeHiSetVal);
            }
            else
            {                                                    // Case: Low voltage & High current
                if ((ScpiQuery<double>("VOLT?")) > RangeLowMaxVolt)
                {
                    ScpiCommand("VOLT 0");
                    SetOutputState(EState.Off);
                }
                ScpiCommand("VOLT:RANG " + RangeLowSetVal);
            }

            QueryErrWithExc(true, "SetVoltageRange " + voltageRangeV);
        }

        /// <inheritdoc />
        public abstract void SetVoltageDcProtectionLimitLower(double voltageDcLimitLow);

        /// <inheritdoc />
        public abstract void SetVoltageDcProtectionLimitUpper(double voltageDcLimitUpp);

        /// <inheritdoc />
        public abstract void SetVoltageDcProtectionState(EState onOff);

        /// <inheritdoc />
        public abstract void SetVoltageMode(EVoltageMode voltageMode);
    }
}
